/* *****************************************************************************
 * JFab (http://code.google.com/p/jfab)
 * Copyright (c) 2011 JFab.org
 * Admin jfab@jeffreythompson.net
 *
 * See the file "LICENSE.txt" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 * *****************************************************************************/

package org.jfab.core.factory;

import java.beans.PropertyChangeSupport;
import java.util.Collections;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.javaruntype.type.Type;
import org.jfab.core.collection.CGAnnotationList;
import org.jfab.core.collection.CGFieldList;
import org.jfab.core.collection.CGMethodList;
import org.jfab.core.collection.CGParameterList;
import org.jfab.core.collection.CGPropertyList;
import org.jfab.core.collection.CGTypeList;
import org.jfab.core.collection.CommentList;
import org.jfab.core.collection.MethodBodyList;
import org.jfab.core.collection.ModifierList;
import org.jfab.core.collection.ParameterNameList;
import org.jfab.core.model.CGField;
import org.jfab.core.model.CGMethod;
import org.jfab.core.model.CGMethodDeclaration;
import org.jfab.core.model.CGParameter;
import org.jfab.core.model.CGProperty;
import org.jfab.core.model.CGType;
import org.jfab.core.model.CGTypeUtilities;
import org.jfab.core.model.Modifier;
import org.jfab.core.util.TypeParameterMap;

/**
 * Provides a method factory.
 */
public final class DefaultCGMethodFactory implements CGMethodFactory
{
    /** Annotation factory. */
    private final CGAnnotationFactory _annotationFactory;

    /** Method declaration factory. */
    private final CGMethodDeclarationFactory _methodDeclarationFactory;

    /** Parameter factory. */
    private final CGParameterFactory _parameterFactory;

    /** String factory. */
    private final StringFactory _stringFactory;

    /** Type factory. */
    private final CGTypeFactory _cgTypeFactory;

    /** Type reference support. */
    private final TypeReferenceSupport _typeReferenceSupport;

    /** Code generator type utilities. */
    private final CGTypeUtilities _cgTypeUtils;

    /**
     * Construct this object with the given parameters.
     * 
     * @param cgTypeUtils Code generator type utilities. (required)
     * @param cgTypeFactory Code generator type factory.
     * @param typeReferenceSupport Type reference support.
     * @param stringFactory String factory.
     * @param annotationFactory Annotation factory.
     * @param parameterFactory Parameter factory.
     * @param methodDeclarationFactory Method declaration factory.
     */
    public DefaultCGMethodFactory(final CGTypeUtilities cgTypeUtils,
            final CGTypeFactory cgTypeFactory,
            final TypeReferenceSupport typeReferenceSupport,
            final StringFactory stringFactory,
            final CGAnnotationFactory annotationFactory,
            final CGParameterFactory parameterFactory,
            final CGMethodDeclarationFactory methodDeclarationFactory)
    {
        _cgTypeUtils = cgTypeUtils;
        _cgTypeFactory = cgTypeFactory;
        _typeReferenceSupport = typeReferenceSupport;
        _stringFactory = stringFactory;
        _annotationFactory = annotationFactory;
        _parameterFactory = parameterFactory;
        _methodDeclarationFactory = methodDeclarationFactory;
    }

    @Override
    public final CGMethod createAccessorMethod(final CGField field)
    {
        final String propertyName = getStringFactory().createPropertyName(
                field.getName());
        final CGAnnotationList annotations = new CGAnnotationList(
                field.getAnnotations());
        final ModifierList modifiers = new ModifierList(Modifier.PUBLIC);
        final CGType propertyType = field.getType();

        return createAccessorMethod(annotations, modifiers, propertyType,
                propertyName);
    }

    @Override
    public final CGMethod createAddPropertyChangeListenerMethod(
            final CGAnnotationList annotations, final ModifierList modifiers)
    {
        final CGMethodDeclarationFactory factory = getMethodDeclarationFactory();
        final CGMethodDeclaration methodDeclaration = factory
                .createAddPropertyChangeListenerMethod(annotations, modifiers);
        final MethodBodyList methodBodyLines = new MethodBodyList(
                Collections
                        .singletonList("getPropertyChangeSupport().addPropertyChangeListener(listener);"));

        return createMethod(methodDeclaration, methodBodyLines);
    }

    @Override
    public final CGMethod createAddPropertyChangeListenerNamedMethod(
            final CGAnnotationList annotations, final ModifierList modifiers)
    {
        final CGMethodDeclarationFactory factory = getMethodDeclarationFactory();
        final CGMethodDeclaration methodDeclaration = factory
                .createAddPropertyChangeListenerNamedMethod(annotations,
                        modifiers);
        final MethodBodyList methodBodyLines = new MethodBodyList(
                Collections
                        .singletonList("getPropertyChangeSupport().addPropertyChangeListener(propertyName, listener);"));

        return createMethod(methodDeclaration, methodBodyLines);
    }

    @Override
    public final CGMethod createCloneMethod()
    {
        final CGTypeList referencedTypes = null;
        final CommentList commentLines = new CommentList(
                Collections.singletonList("@see  java.lang.Object#clone()"));
        final CGAnnotationFactory annotationFactory = getAnnotationFactory();
        final CGAnnotationList annotations = new CGAnnotationList(
                Collections.singletonList(annotationFactory
                        .createOverrideAnnotation()));
        final ModifierList modifiers = new ModifierList(Modifier.PUBLIC);
        final CGType returnType = getTypeFactory().createType(Object.class);
        final String methodName = "clone";
        final CGParameterList parameters = null;
        final CGTypeList throwsTypes = null;
        final MethodBodyList methodBodyLines = new MethodBodyList();
        methodBodyLines.add("try");
        methodBodyLines.add("{");
        methodBodyLines.add("return super.clone();");
        methodBodyLines.add("}");
        methodBodyLines.add("catch (CloneNotSupportedException e)");
        methodBodyLines.add("{");
        methodBodyLines
                .add("// This should not happen, since this class is Cloneable.");
        methodBodyLines.add("throw new InternalError();");
        methodBodyLines.add("}");

        return createMethod(referencedTypes, commentLines, annotations,
                modifiers, returnType, methodName, parameters, throwsTypes,
                methodBodyLines);
    }

    @Override
    public final CGMethod createDelegateMethod(final CommentList commentLines,
            final CGAnnotationList annotations, final ModifierList modifiers,
            final CGType returnType, final String methodName,
            final CGParameterList parameters, final String fieldName)
    {
        String argumentsString = "";

        if (parameters != null)
        {
            final ParameterNameList parameterNameList = new ParameterNameList(
                    parameters);
            argumentsString = getStringFactory().createArgumentsString(
                    parameterNameList);
        }

        final CGTypeList referencedTypes = null;
        final CGTypeList throwsTypes = null;

        final StringBuilder sb = new StringBuilder();

        if ((returnType != null))
        {
            final Type<?> type = _cgTypeUtils.getType(returnType);

            if ((type != null) && (type.getRawClass() != Void.class))
            {
                sb.append("return ");
            }
        }

        sb.append(fieldName);
        sb.append(".");
        sb.append(methodName);
        sb.append("(");
        sb.append(argumentsString);
        sb.append(");");

        final MethodBodyList methodBodyLines = new MethodBodyList(
                Collections.singletonList(sb.toString()));

        return createMethod(referencedTypes, commentLines, annotations,
                modifiers, returnType, methodName, parameters, throwsTypes,
                methodBodyLines);
    }

    @Override
    public final CGMethod createEqualsMethod(final String classname,
            final CGFieldList fields)
    {
        final CGTypeList referencedTypes = new CGTypeList();
        referencedTypes.add(getTypeFactory().createType(EqualsBuilder.class));

        final CommentList commentLines = new CommentList(
                Collections
                        .singletonList("@return true if this equals object."));
        final CGAnnotationList annotations = new CGAnnotationList(
                Collections.singletonList(getAnnotationFactory()
                        .createOverrideAnnotation()));
        final ModifierList modifiers = new ModifierList(Modifier.PUBLIC);
        final CGType returnType = getTypeFactory().createType(boolean.class);
        final String methodName = "equals";
        final CGParameter parameter0 = getParameterFactory().createParameter(
                getTypeFactory().createType(Object.class), "object");
        final CGParameterList parameters = new CGParameterList(
                Collections.singletonList(parameter0));
        final CGTypeList throwsTypes = null;
        final MethodBodyList methodBodyLines = new MethodBodyList();
        methodBodyLines.add("boolean answer = false;");
        methodBodyLines.add("if ((object instanceof " + classname
                + ") == false)");
        methodBodyLines.add("{");
        methodBodyLines.add("answer = false;");
        methodBodyLines.add("}");
        methodBodyLines.add("else if (this == object)");
        methodBodyLines.add("{");
        methodBodyLines.add("answer = true;");
        methodBodyLines.add("}");
        methodBodyLines.add("else");
        methodBodyLines.add("{");
        if (!fields.isEmpty())
        {
            methodBodyLines
                    .add(classname + " rhs = (" + classname + ")object;");
        }
        methodBodyLines.add("answer = new EqualsBuilder()");
        methodBodyLines.add(".appendSuper(super.equals(object))");
        for (final CGField field : fields)
        {
            if (field.isUsedInIdentity() == Boolean.TRUE)
            {
                methodBodyLines.add(".append(" + field.getName() + ", rhs."
                        + field.getName() + ")");
            }
        }
        methodBodyLines.add(".isEquals();");
        methodBodyLines.add("}");
        methodBodyLines.add("return answer;");

        return createMethod(referencedTypes, commentLines, annotations,
                modifiers, returnType, methodName, parameters, throwsTypes,
                methodBodyLines);
    }

    @Override
    public final CGMethod createGetInstanceFactoryMethod()
    {
        final CGTypeList referencedTypes = new CGTypeList();
        referencedTypes.add(getTypeFactory().createType(
                DefaultInstanceFactory.class));

        final CGAnnotationList annotations = null;
        final ModifierList modifiers = new ModifierList(Modifier.PRIVATE);
        final CGType propertyType = getTypeFactory().createType(
                DefaultInstanceFactory.class);
        final String propertyName = "instanceFactory";
        final ParameterNameList constructorArgs = null;

        return createLazyInitializationAccessorMethod(referencedTypes,
                annotations, modifiers, propertyType, propertyName,
                constructorArgs);
    }

    @Override
    public final CGMethod createGetPropertyChangeSupportMethod()
    {
        final CGType type = getTypeFactory().createType(
                PropertyChangeSupport.class);
        getTypeReferenceSupport().addReferencedType(type);

        final CGTypeList referencedTypes = new CGTypeList();
        referencedTypes.add(type);

        final CGAnnotationList annotations = null;
        final ModifierList modifiers = new ModifierList(Modifier.PRIVATE);
        final CGType propertyType = getTypeFactory().createType(
                PropertyChangeSupport.class);
        final String propertyName = "propertyChangeSupport";
        final ParameterNameList constructorArgs = new ParameterNameList(
                Collections.singletonList("this"));

        return createLazyInitializationAccessorMethod(referencedTypes,
                annotations, modifiers, propertyType, propertyName,
                constructorArgs);
    }

    @Override
    public final CGMethod createHashCodeMethod(final CGFieldList fields)
    {
        final CGTypeList referencedTypes = new CGTypeList();
        referencedTypes.add(getTypeFactory().createType(HashCodeBuilder.class));

        final CommentList commentLines = new CommentList(
                Collections.singletonList("@return a hash code."));
        final CGAnnotationList annotations = new CGAnnotationList(
                Collections.singletonList(getAnnotationFactory()
                        .createOverrideAnnotation()));
        final ModifierList modifiers = new ModifierList(Modifier.PUBLIC);
        final CGType returnType = getTypeFactory().createType(int.class);
        final String methodName = "hashCode";
        final CGParameterList parameters = null;
        final CGTypeList throwsTypes = null;
        final MethodBodyList methodBodyLines = new MethodBodyList();
        methodBodyLines
                .add("// you pick a hard-coded, randomly chosen, non-zero, odd number");
        methodBodyLines.add("// ideally different for each class");
        methodBodyLines.add("return new HashCodeBuilder(17, 37)");
        methodBodyLines.add(".appendSuper(super.hashCode())");
        for (final CGField field : fields)
        {
            if (field.isUsedInIdentity() == Boolean.TRUE)
            {
                methodBodyLines.add(".append(" + field.getName() + ")");
            }
        }
        methodBodyLines.add(".toHashCode();");

        return createMethod(referencedTypes, commentLines, annotations,
                modifiers, returnType, methodName, parameters, throwsTypes,
                methodBodyLines);
    }

    @Override
    public final CGMethod createLazyInitializationAccessorMethod(
            final CGTypeList referencedTypes,
            final CGAnnotationList annotations, final ModifierList modifiers,
            final CGType propertyType, final String propertyName,
            final ParameterNameList constructorArgs)
    {
        final String constructorArgsString = getStringFactory()
                .createArgumentsString(constructorArgs);
        final String typeString = getTypeReferenceSupport().createTypeString(
                propertyType);

        final CommentList commentLines = new CommentList(
                Collections.singletonList("@return " + propertyName));
        final CGType returnType = propertyType;
        final String methodName = getStringFactory().createAccessorNameFor(
                propertyType, propertyName);
        final CGParameterList parameters = null;
        final CGTypeList throwsTypes = null;
        final String memberVariableName = getStringFactory()
                .createMemberVariableName(propertyName);
        final MethodBodyList methodBodyLines = new MethodBodyList();
        methodBodyLines.add("if (" + memberVariableName + " == null)");
        methodBodyLines.add("{");
        methodBodyLines.add(memberVariableName + " = new " + typeString + "("
                + constructorArgsString + ");");
        methodBodyLines.add("}");
        methodBodyLines.add("");
        methodBodyLines.add("return " + memberVariableName + ";");

        return createMethod(referencedTypes, commentLines, annotations,
                modifiers, returnType, methodName, parameters, throwsTypes,
                methodBodyLines);
    }

    @Override
    public CGMethod createMethod(final CGMethodDeclaration methodDeclaration,
            final MethodBodyList methodBodyLines)
    {
        final CGTypeList referencedTypes = new CGTypeList(
                methodDeclaration.getReferencedTypes());
        final CommentList commentLines = new CommentList(
                methodDeclaration.getCommentLines());
        final CGAnnotationList annotations = new CGAnnotationList(
                methodDeclaration.getAnnotations());
        final ModifierList modifiers = new ModifierList(
                methodDeclaration.getModifiers());
        final CGType returnType = methodDeclaration.getReturnType();
        final String methodName = methodDeclaration.getName();
        final CGParameterList parameters = new CGParameterList(
                methodDeclaration.getParameters());
        final CGTypeList throwsTypes = new CGTypeList(
                methodDeclaration.getThrowsTypes());

        return createMethod(referencedTypes, commentLines, annotations,
                modifiers, returnType, methodName, parameters, throwsTypes,
                methodBodyLines);
    }

    @Override
    public final CGMethod createMethod(final CGTypeList referencedTypes,
            final CommentList commentLines, final CGAnnotationList annotations,
            final ModifierList modifiers, final CGType returnType,
            final String methodName, final CGParameterList parameters,
            final CGTypeList throwsTypes, final MethodBodyList methodBodyLines)
    {
        Validate.notEmpty(methodName, "methodName is empty");

        final CGMethod answer = new CGMethod();

        if (referencedTypes != null)
        {
            answer.getReferencedTypes().addAll(referencedTypes);
        }

        if (commentLines != null)
        {
            answer.getCommentLines().addAll(commentLines);
        }

        if (annotations != null)
        {
            answer.getAnnotations().addAll(annotations);
        }

        if (modifiers != null)
        {
            answer.getModifiers().addAll(modifiers);
        }

        if (returnType != null)
        {
            answer.setReturnType(returnType);
        }

        answer.setName(methodName);

        if (parameters != null)
        {
            answer.getParameters().addAll(parameters);
        }

        if (throwsTypes != null)
        {
            answer.getThrowsTypes().addAll(throwsTypes);
        }

        if (methodBodyLines != null)
        {
            answer.getMethodBodyLines().addAll(methodBodyLines);
        }

        return answer;
    }

    @Override
    public final CGMethod createMutatorMethod(final CGField field)
    {
        final String propertyName = getStringFactory().createPropertyName(
                field.getName());
        final String memberVariableName = field.getName();
        final String parameterName = propertyName + "In";
        final String paramComment = getStringFactory()
                .createCommentFromPropertyName(propertyName);

        final CGTypeList referencedTypes = new CGTypeList(
                field.getReferencedTypes());
        final CommentList commentLines = new CommentList(
                Collections.singletonList("@param " + parameterName + "  "
                        + paramComment));
        final CGAnnotationList annotations = null;
        final ModifierList modifiers = new ModifierList(Modifier.PUBLIC);
        final CGType returnType = null;
        final String methodName = getStringFactory().createMutatorNameFor(
                propertyName);

        final CGParameter parameter = new CGParameter();
        parameter.setName(parameterName);
        parameter.setType(field.getType());
        final CGParameterList parameters = new CGParameterList();
        parameters.add(parameter);

        final CGTypeList throwsTypes = null;
        final MethodBodyList methodBodyLines = new MethodBodyList();
        methodBodyLines.add(memberVariableName + " = " + parameterName + ";");

        return createMethod(referencedTypes, commentLines, annotations,
                modifiers, returnType, methodName, parameters, throwsTypes,
                methodBodyLines);
    }

    @Override
    public final CGMethod createMutatorPropertyChangeMethod(
            final CGAnnotationList annotations, final ModifierList modifiers,
            final CGType propertyType, final String propertyName,
            final String paramComment)
    {
        final String parameterName = propertyName + "In";
        final String typeString = getTypeReferenceSupport().createTypeString(
                propertyType);
        final String constantName = getStringFactory()
                .createPropertyChangeName(propertyName);

        final CGTypeList referencedTypes = null;
        final CommentList commentLines = new CommentList(
                Collections.singletonList("@param " + parameterName + "  "
                        + paramComment));
        final CGType returnType = null;
        final String methodName = getStringFactory().createMutatorNameFor(
                propertyName);
        final CGParameter parameter0 = getParameterFactory().createParameter(
                propertyType, parameterName);
        final CGParameterList parameters = new CGParameterList();
        parameters.add(parameter0);

        final CGTypeList throwsTypes = null;
        final String memberVariableName = getStringFactory()
                .createMemberVariableName(propertyName);
        final MethodBodyList methodBodyLines = new MethodBodyList();
        methodBodyLines.add(typeString + " oldValue = " + memberVariableName
                + ";");
        methodBodyLines.add(memberVariableName + " = " + parameterName + ";");
        methodBodyLines.add("");
        methodBodyLines.add("getPropertyChangeSupport().firePropertyChange("
                + constantName + ", oldValue, " + memberVariableName + ");");

        return createMethod(referencedTypes, commentLines, annotations,
                modifiers, returnType, methodName, parameters, throwsTypes,
                methodBodyLines);
    }

    @Override
    public final CGMethod createMutatorPropertyChangeMethod(final CGField field)
    {
        final String propertyName = getStringFactory().createPropertyName(
                field.getName());
        final String memberVariableName = field.getName();
        final String parameterName = propertyName + "In";
        final String paramComment = getStringFactory()
                .createCommentFromPropertyName(propertyName);

        final CGTypeList referencedTypes = new CGTypeList(
                field.getReferencedTypes());
        final CommentList commentLines = new CommentList(
                Collections.singletonList("@param " + parameterName + "  "
                        + paramComment));
        final CGAnnotationList annotations = null;
        final ModifierList modifiers = new ModifierList(Modifier.PUBLIC);
        final CGType returnType = null;
        final String methodName = getStringFactory().createMutatorNameFor(
                propertyName);

        final CGParameter parameter = new CGParameter();
        parameter.setName(parameterName);
        parameter.setType(field.getType());
        final CGParameterList parameters = new CGParameterList();
        parameters.add(parameter);

        final CGTypeList throwsTypes = null;
        final String typeString = getTypeReferenceSupport().createTypeString(
                field.getType());
        final String constantName = getStringFactory()
                .createPropertyChangeName(propertyName);
        final MethodBodyList methodBodyLines = new MethodBodyList();
        methodBodyLines.add(typeString + " oldValue = " + memberVariableName
                + ";");
        methodBodyLines.add(memberVariableName + " = " + parameterName + ";");
        methodBodyLines.add("");
        methodBodyLines.add("getPropertyChangeSupport().firePropertyChange("
                + constantName + ", oldValue, " + memberVariableName + ");");

        return createMethod(referencedTypes, commentLines, annotations,
                modifiers, returnType, methodName, parameters, throwsTypes,
                methodBodyLines);
    }

    @Override
    public final CGMethodList createPropertyAccessorMethods(
            final CGAnnotationList annotations, final ModifierList modifiers,
            final CGPropertyList properties,
            final TypeParameterMap typeParameterMap)
    {
        final CGMethodList answer = new CGMethodList();

        for (final CGProperty property : properties)
        {
            if (isGetter(property))
            {
                final CGType propertyType = typeParameterMap == null ? property
                        .getType() : typeParameterMap.mapType(property
                        .getType());
                final String propertyName = property.getName();
                final CGMethod method = createAccessorMethod(annotations,
                        modifiers, propertyType, propertyName);
                answer.add(method);
            }
        }

        return answer;
    }

    @Override
    public final CGMethodList createPropertyMutatorMethods(
            final CGAnnotationList annotations, final ModifierList modifiers,
            final CGPropertyList properties,
            final TypeParameterMap typeParameterMap)
    {
        final CGMethodList answer = new CGMethodList();

        for (final CGProperty property : properties)
        {
            if (isSetter(property))
            {
                if ((property.getModifiers() == null)
                        || ((property.getModifiers() != null) && !property
                                .getModifiers().contains(
                                        Modifier.FINAL.getName())))
                {
                    final CGType propertyType = typeParameterMap == null ? property
                            .getType() : typeParameterMap.mapType(property
                            .getType());
                    final String propertyName = property.getName();
                    final String paramComment = getStringFactory()
                            .createCommentFromPropertyName(propertyName);
                    final CGMethod method = createMutatorMethod(annotations,
                            modifiers, propertyType, propertyName, paramComment);
                    answer.add(method);
                }
            }
        }

        return answer;
    }

    @Override
    public final CGMethodList createPropertyMutatorPropertyChangeMethods(
            final CGAnnotationList annotations, final ModifierList modifiers,
            final CGPropertyList properties)
    {
        final CGMethodList answer = new CGMethodList();

        if (CollectionUtils.isNotEmpty(properties))
        {
            for (final CGProperty property : properties)
            {
                if (isSetter(property))
                {
                    if ((property.getModifiers() == null)
                            || ((property.getModifiers() != null) && !property
                                    .getModifiers().contains(
                                            Modifier.FINAL.getName())))
                    {
                        final CGType propertyType = property.getType();
                        final String propertyName = property.getName();
                        final String paramComment = propertyName;
                        final CGMethod newMethod = createMutatorPropertyChangeMethod(
                                annotations, modifiers, propertyType,
                                propertyName, paramComment);
                        answer.add(newMethod);
                    }
                }
            }
        }

        return answer;
    }

    @Override
    public final CGMethod createRemovePropertyChangeListenerMethod(
            final CGAnnotationList annotations, final ModifierList modifiers)
    {
        final CGMethodDeclarationFactory factory = getMethodDeclarationFactory();
        final CGMethodDeclaration methodDeclaration = factory
                .createRemovePropertyChangeListenerMethod(annotations,
                        modifiers);
        final MethodBodyList methodBodyLines = new MethodBodyList(
                Collections
                        .singletonList("getPropertyChangeSupport().removePropertyChangeListener(listener);"));

        return createMethod(methodDeclaration, methodBodyLines);
    }

    @Override
    public final CGMethod createRemovePropertyChangeListenerNamedMethod(
            final CGAnnotationList annotations, final ModifierList modifiers)
    {
        final CGMethodDeclarationFactory factory = getMethodDeclarationFactory();
        final CGMethodDeclaration methodDeclaration = factory
                .createRemovePropertyChangeListenerNamedMethod(annotations,
                        modifiers);
        final MethodBodyList methodBodyLines = new MethodBodyList(
                Collections
                        .singletonList("getPropertyChangeSupport().removePropertyChangeListener(propertyName, listener);"));

        return createMethod(methodDeclaration, methodBodyLines);
    }

    @Override
    public final CGMethod createToStringMethod()
    {
        final CGTypeList referencedTypes = new CGTypeList();
        referencedTypes.add(getTypeFactory().createType(ToStringBuilder.class));
        referencedTypes.add(getTypeFactory().createType(ToStringStyle.class));

        final CommentList commentLines = new CommentList(
                Collections
                        .singletonList("@return a string representation of this object."));
        final CGAnnotationList annotations = new CGAnnotationList(
                Collections.singletonList(getAnnotationFactory()
                        .createOverrideAnnotation()));
        final ModifierList modifiers = new ModifierList(Modifier.PUBLIC);
        final CGType returnType = getTypeFactory().createType(String.class);
        final String methodName = "toString";
        final CGParameterList parameters = null;
        final CGTypeList throwsTypes = null;
        final MethodBodyList methodBodyLines = new MethodBodyList(
                Collections
                        .singletonList("return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);"));

        return createMethod(referencedTypes, commentLines, annotations,
                modifiers, returnType, methodName, parameters, throwsTypes,
                methodBodyLines);
    }

    /**
     * @param annotations Annotations.
     * @param modifiers Modifiers.
     * @param propertyType Property type.
     * @param propertyName Property name.
     * 
     * @return an accessor method.
     */
    private CGMethod createAccessorMethod(final CGAnnotationList annotations,
            final ModifierList modifiers, final CGType propertyType,
            final String propertyName)
    {
        final CGTypeList referencedTypes = null;
        final CommentList commentLines = new CommentList(
                Collections.singletonList("@return " + propertyName));
        final CGType returnType = propertyType;
        final String methodName = getStringFactory().createAccessorNameFor(
                propertyType, propertyName);
        final CGParameterList parameters = null;
        final CGTypeList throwsTypes = null;
        final String memberVariableName = getStringFactory()
                .createMemberVariableName(propertyName);
        final MethodBodyList methodBodyLines = new MethodBodyList(
                Collections.singletonList("return " + memberVariableName + ";"));

        return createMethod(referencedTypes, commentLines, annotations,
                modifiers, returnType, methodName, parameters, throwsTypes,
                methodBodyLines);
    }

    /**
     * @param annotations Annotations.
     * @param modifiers Modifiers.
     * @param propertyType Property type.
     * @param propertyName Property name.
     * @param paramComment Parameter comment.
     * 
     * @return a mutator method.
     */
    private CGMethod createMutatorMethod(final CGAnnotationList annotations,
            final ModifierList modifiers, final CGType propertyType,
            final String propertyName, final String paramComment)
    {
        final CGTypeList referencedTypes = null;
        final CommentList commentLines = new CommentList(
                Collections.singletonList("@param " + propertyName + "  "
                        + paramComment));
        final CGType returnType = null;
        final String methodName = getStringFactory().createMutatorNameFor(
                propertyName);
        final CGParameter parameter0 = getParameterFactory().createParameter(
                propertyType, propertyName);
        final CGParameterList parameters = new CGParameterList();
        parameters.add(parameter0);

        final CGTypeList throwsTypes = null;
        final String memberVariableName = getStringFactory()
                .createMemberVariableName(propertyName);
        final MethodBodyList methodBodyLines = new MethodBodyList(
                Collections.singletonList(memberVariableName + " = "
                        + propertyName + ";"));

        return createMethod(referencedTypes, commentLines, annotations,
                modifiers, returnType, methodName, parameters, throwsTypes,
                methodBodyLines);
    }

    /**
     * @return the annotationFactory
     */
    private CGAnnotationFactory getAnnotationFactory()
    {
        return _annotationFactory;
    }

    /**
     * @return the methodDeclarationFactory
     */
    private CGMethodDeclarationFactory getMethodDeclarationFactory()
    {
        return _methodDeclarationFactory;
    }

    /**
     * @return the parameterFactory
     */
    private CGParameterFactory getParameterFactory()
    {
        return _parameterFactory;
    }

    /**
     * @return the stringFactory
     */
    private StringFactory getStringFactory()
    {
        return _stringFactory;
    }

    /**
     * @return the typeFactory
     */
    private CGTypeFactory getTypeFactory()
    {
        return _cgTypeFactory;
    }

    /**
     * @return the typeReferenceSupport
     */
    private TypeReferenceSupport getTypeReferenceSupport()
    {
        return _typeReferenceSupport;
    }

    /**
     * @param cgProperty Code generator field.
     * 
     * @return true if the given parameter has a getter method.
     */
    private boolean isGetter(final CGProperty cgProperty)
    {
        return (cgProperty.isGetter() != null) && cgProperty.isGetter();
    }

    /**
     * @param cgProperty Code generator field.
     * 
     * @return true if the given parameter has a setter method.
     */
    private boolean isSetter(final CGProperty cgProperty)
    {
        return (cgProperty.isSetter() != null) && cgProperty.isSetter();
    }
}
